package org.goldfish.demo.multifrontend.messageboard.mobile.network

import android.util.Base64
import org.json.JSONObject
// Import new DTOs
// Old Message DTO import is removed
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.core.content.edit
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.goldfish.demo.multifrontend.messageboard.mobile.model.AddMessageRequest
import org.goldfish.demo.multifrontend.messageboard.mobile.model.AddedMessageResponse
import org.goldfish.demo.multifrontend.messageboard.mobile.model.JwtInfo
import org.goldfish.demo.multifrontend.messageboard.mobile.model.LoginReq
import org.goldfish.demo.multifrontend.messageboard.mobile.model.MessageView
import org.goldfish.demo.multifrontend.messageboard.mobile.model.SseEmitter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.lang.reflect.Type
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeParseException
import java.util.Date
import java.util.Properties

object ApiClient {
    private val TAG = ApiClient::class.java.simpleName
    lateinit var BASE_URL: String
    private lateinit var retrofit: Retrofit
    private lateinit var prefs: SharedPreferences // ApiClient's own SharedPreferences for token

    private const val PREFS_NAME =
        "message_board_prefs" // Same name as PrefsHelper, ensure distinct keys if used together
    private const val KEY_TOKEN = "token" // ApiClient's own key for token

    val auth: Pair<String, String>
        get() {
            return Pair(
                prefs.getString(PrefsHelper.KEY_USERNAME, "") ?: "",
                prefs.getString(PrefsHelper.KEY_PASSWORD, "") ?: ""
            )
        }
    val lastLoginTime: LocalDateTime
        get() = PrefsHelper.getLastLoginTime() ?: LocalDateTime.now()

    fun initialize(app: Application) {
        prefs = app.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

        PrefsHelper.initialize(app)
        loadIPInfo(app)

        // Initialize ApiClient's SharedPreferences for token
        prefs = app.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

        val okHttpClient = OkHttpClient.Builder().addInterceptor { chain ->
            val request = chain.request()
            Logger.d("Requesting ${request.url}")
            val res = chain.proceed(request)
            // Consider logging response body carefully due to size/one-time read nature
            Logger.d("${request.url} -> Response received for " + request.method)
            res
        }.addInterceptor(HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }).build()

        val gson = GsonBuilder().registerTypeAdapter(
            LocalDateTime::class.java, object : JsonSerializer<LocalDateTime> {
                override fun serialize(
                    src: LocalDateTime?, typeOfSrc: Type?, context: JsonSerializationContext?
                ): JsonElement {
                    return JsonPrimitive(src?.toString() ?: "")
                }
            }).registerTypeAdapter(
            LocalDateTime::class.java, object : JsonDeserializer<LocalDateTime> {
                override fun deserialize(
                    json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?
                ): LocalDateTime? {
                    return try {
                        json?.asString?.let { LocalDateTime.parse(it) }
                    } catch (e: Exception) {
                        Logger.e("Error parsing LocalDateTime: ${json?.asString}", e)
                        throw JsonParseException(
                            "Could not parse LocalDateTime: ${json?.asString}", e
                        )
                    }
                }
            }).create()

        retrofit = Retrofit.Builder().baseUrl(BASE_URL).client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(gson)).build()
    }

    suspend fun auth(
        req: LoginReq = LoginReq("", ""), isRegister: Boolean = false
    ): String {
        run tryValidCache@{
            when (PrefsHelper.cacheUserNameAndPassword(req.username, req.password)) {
                false -> {
                    val bufferedPrepareDate = LocalDateTime.now().plusSeconds(20)
                    val token = Token
                    if (token.isBlank())
                        return@tryValidCache
                    val result = NetUtils.parseJwtPayload(token)

                    // 把 Date 转成 LocalDateTime
                    val expLdt = LocalDateTime.ofInstant(
                        result.expiration.toInstant(), ZoneId.systemDefault()
                    )

                    // 如果过期时间在缓冲时间之前，就判定无效
                    if (expLdt.isBefore(bufferedPrepareDate)) return@tryValidCache

                    return token
                }

                null -> {
                    return ""
                }

                else -> {}
            }
        }
        Logger.i("handleAuth: $req")

        val response = if (isRegister) {
            api.register(req)
        } else {
            api.login(req)
        }
        val token = response["token"]
        if (token.isNullOrBlank()) {
            throw IllegalStateException("Empty token in response")
        }
        PrefsHelper.saveToken(token)
        PrefsHelper.saveLastLoginTime(LocalDateTime.now()) // Save login time
        return token
    }

    fun loadIPInfo(app: Application): String {
        val props = Properties()
        app.assets.open("config.properties").use { props.load(it) }

        val domain = props.getProperty("DOMAIN") ?: "10.0.2.2"
        val port = props.getProperty("PORT")

        BASE_URL = when (port) {
            null -> "https://$domain/"
            "" -> "https://$domain/"
            else -> "https://$domain:$port/"
        }
        Logger.d("BASE_URL initialized: $BASE_URL")

        return BASE_URL
    }

    val api: ApiService
        get() = retrofit.create(ApiService::class.java)
    val hasAuth: Boolean?
        get() = PrefsHelper.cacheUserNameAndPassword()

    suspend fun getAllMessagesWithDefaultToken(): List<MessageView> {
        return api.getAllMessages("Bearer $Token")
    }

    suspend fun getMessagesWithDefaultToken(): List<MessageView> {
        return getAllMessagesWithDefaultToken()
    }

    // Updated postMessageWithDefaultToken
    suspend fun postMessageWithDefaultToken(
        message: AddMessageRequest, // Changed from Message
        token: String? = null
    ): AddedMessageResponse { // Changed from Message
        val actualToken = token ?: Token // Uses ApiClient's own prefs for token
        return api.addMessage(
            token = "Bearer $actualToken", message = message
        )
    }

    suspend fun deleteMessageWithDefaultToken(
        id: Long, token: String? = null
    ) {
        val actualToken = token ?: Token
        api.deleteMessage("Bearer $actualToken", id)
    }

    // getToken uses ApiClient's own SharedPreferences instance
    val Token: String
        get() {
            if (!::prefs.isInitialized) {
                throw IllegalStateException("ApiClient未初始化，请先调用initialize方法")
            }
            return PrefsHelper.getToken() ?: ""
        }

    suspend fun getMessagesByUserWithDefaultToken(
        userName: String
    ): List<MessageView> {
        return api.getMessagesByUser("Bearer $Token", userName)
    }

    suspend fun subscribeWithDefaultToken(
        token: String? = null
    ): SseEmitter {
        return api.subscribe("Bearer $Token")
    }

    suspend fun getMessagesPageWithDefaultToken(
        page: Int, size: Int, token: String? = null
    ): List<MessageView> {
        return api.getMessagesPage("Bearer $Token", page, size)
    }

    private object PrefsHelper {
        const val PREFS_NAME = "message_board_prefs"
        const val KEY_USERNAME = "username"
        const val KEY_PASSWORD = "password"
        const val KEY_TOKEN = "token"
        const val KEY_LAST_LOGIN_TIME = "last_login_time"

//         lateinit var prefs: SharedPreferences


        fun initialize(context: Context) {
//            prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
            saveToken(null)
        }

        /**
         * Caches the username and password for later use.
         * @return true if the username and password were cached, false if they were already cached, and null if there was an error.
         */
        fun cacheUserNameAndPassword(username: String = "", password: String = ""): Boolean? {
            val oldUsername = prefs.getString(KEY_USERNAME, null)
            val oldPassword = prefs.getString(KEY_PASSWORD, null)
            if (username == "" || password == "") return null

            if (oldUsername == username && oldPassword == password) return false
            prefs.edit {
                putString(KEY_USERNAME, username)
                putString(KEY_PASSWORD, password)
            }
            return true
        }


        fun saveToken(token: String?) {
            prefs.edit {
                putString(KEY_TOKEN, token)
            }
        }

        fun getToken(): String? {
            return prefs.getString(KEY_TOKEN, null)
        }

        // New function to save last login time
        fun saveLastLoginTime(time: LocalDateTime) {
            if (!::prefs.isInitialized) {
                Log.w(
                    TAG,
                    "PrefsHelper not initialized when trying to save last login time. Call initialize first."
                )
                return
            }
            try {
                val timeString = time.toString() // LocalDateTime.toString() gives ISO 8601 format
                prefs.edit { putString(KEY_LAST_LOGIN_TIME, timeString) }
            } catch (e: Exception) {
                Log.e(TAG, "Error saving last login time", e)
            }
        }

        // New function to get last login time
        fun getLastLoginTime(): LocalDateTime? {
            if (!::prefs.isInitialized) {
                Log.w(
                    TAG,
                    "PrefsHelper not initialized when trying to get last login time. Call initialize first."
                )
                return null
            }
            val timeString = prefs.getString(KEY_LAST_LOGIN_TIME, null)
            return if (timeString != null) {
                try {
                    LocalDateTime.parse(timeString) // LocalDateTime.parse() handles ISO 8601
                } catch (e: DateTimeParseException) {
                    Log.e(TAG, "Error parsing saved last login time: $timeString", e)
                    null
                } catch (e: Exception) {
                    Log.e(TAG, "Unexpected error getting last login time", e)
                    null
                }
            } else {
                null
            }
        }

    }

    private object NetUtils {


        fun parseJwtPayload(jwt: String): JwtInfo {
            val parts = jwt.split(".")
            require(parts.size == 3) { "Invalid JWT format" }

            val payloadJson = String(base64UrlDecode(parts[1]))
            val payload = JSONObject(payloadJson)

            return JwtInfo(
                username = payload.getString("sub"),
                expiration = Date(payload.getLong("exp") * 1000)
            )
        }

        fun base64UrlDecode(str: String): ByteArray {
            var s = str
            val mod = s.length % 4
            if (mod > 0) s += "=".repeat(4 - mod)
            return Base64.decode(s, Base64.URL_SAFE)
        }

    }
}
